feat: AI commit-message sub-agent with live streaming + commit bar controls#38
Conversation
…ntrols Add an isolated, tool-less one-shot Claude Agent SDK run that proposes commit messages from the staged diff and streams them live into the git panel over a dedicated git:commit-message-stream IPC event — never through the conversation store or agent lifecycle. - GitManager.buildCommitContext: main-side staged diff (60 KB cap), binary-flagged file list, recent subjects for style inference, all argv-only git with credential redaction - AgentManager.generateCommitMessage: pinned Haiku model, maxTurns 1, allowedTools [] + deny-all canUseTool, no resume/transcript/MCP, cancelable per workspace and hooked into cleanup() - Commit bar: narrow Commit button plus Stage all / Unstage all and a Generate/Cancel control in one row, same tokens and design language - useGitStore: draft lives in the store, streams append live, failed or canceled runs restore the user's pre-generation draft - Hardened IPC: sender-validated handle(), assertId-only renderer input, GIT_LIMITS.commitGen caps, redacted errors/logs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
This PR successfully implements AI-powered commit message generation using the Claude agent. The implementation follows secure coding practices with proper input validation, error handling, and resource cleanup. The feature integrates cleanly with the existing architecture and doesn't introduce any security vulnerabilities or functional defects.
Key strengths:
- Secure execution with proper input validation and size caps
- Well-structured error handling with classification and recovery
- Clean separation between commit generation and the main agent workflow
- Proper cancellation and cleanup mechanisms
- Follows established patterns for SDK integration and IPC communication
No blocking issues found. The code is ready to merge.
You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.
📝 WalkthroughWalkthroughThis PR adds an AI-powered Git commit message generation feature. It introduces shared types and IPC channels for commit-message streaming, a ChangesAI Commit Message Generation
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant GitPanel
participant useGitStore
participant Preload
participant gitHandlers
participant GitManager
participant AgentManager
GitPanel->>useGitStore: generateCommitMessage()
useGitStore->>Preload: api.generateCommitMessage(workspaceId)
Preload->>gitHandlers: invoke gitCommitMessageGenerate
gitHandlers->>GitManager: buildCommitContext(workspaceId)
GitManager-->>gitHandlers: GitCommitContext
gitHandlers->>AgentManager: generateCommitMessage(wsId, ctx)
AgentManager-->>Preload: gitCommitMessageStream (delta events)
Preload-->>useGitStore: onCommitMessageStream delta
AgentManager-->>gitHandlers: GenerateCommitMessageResult
gitHandlers-->>Preload: result
Preload-->>useGitStore: done
useGitStore-->>GitPanel: commitMessage updated
sequenceDiagram
participant GitPanel
participant useGitStore
participant Preload
participant AgentManager
GitPanel->>useGitStore: cancelCommitMessage()
useGitStore->>Preload: api.cancelCommitMessage(workspaceId)
Preload->>AgentManager: invoke gitCommitMessageCancel
AgentManager-->>Preload: gitCommitMessageStream (canceled)
Preload-->>useGitStore: onCommitMessageStream canceled
useGitStore-->>GitPanel: draft restored, generatingMessage=false
Estimated code review effort: 4 (Complex) | ~60 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/main/managers/AgentManager.ts`:
- Around line 962-967: Cap the non-diff sections of the commit prompt in
buildCommitPrompt() so staged paths and recent subjects cannot push the prompt
over AGENT_LIMITS.promptMax. Update the prompt assembly in AgentManager to
truncate or limit the 200 staged paths and 20 recent subjects before
concatenation, and keep the existing prompt.length guard as a final safety
check. Locate the change in buildCommitPrompt() and the commit prompt generation
path used by commitGen so the full prompt stays bounded even when the diff is
already within commitGen.diffCharsMax.
In `@src/main/managers/GitManager.ts`:
- Around line 296-301: The staged-file list is being silently truncated by the
`.slice(0, caps.filesMax)` in GitManager’s commit context assembly, so add a
`filesTruncated` flag to `GitCommitContext` and set it when staged files exceed
the cap. Update the context-building logic that returns from
`status(...)`/commit prompt preparation to carry this boolean alongside
`diffTruncated`, and adjust `buildCommitPrompt` to explicitly mention when
staged files were omitted so the `Staged files (...)` header does not
under-report without warning.
- Around line 313-318: The diff handling in GitManager’s cached diff flow is
redacting too late, so partial credentials can survive truncation. Update the
logic around runGit, rawDiff, and redactRemote so the full raw diff is passed
through redactRemote first, and only then slice the redacted result to
caps.diffCharsMax if needed. Keep the existing diffTruncated behavior, but make
sure the final diff assigned for the sub-agent prompt always comes from the
redacted text, not the pre-redaction slice.
In `@src/renderer/features/git/GitPanel.tsx`:
- Around line 319-322: The commit message template seeding in GitPanel is only
triggered by changes to template, so it won’t run again when the active
workspace changes and commitMessage is reset. Update the useEffect in GitPanel
to also depend on the active workspace state (or move the template seeding into
the workspace reset handler) so the draft is reinitialized for each newly active
workspace; use the existing useGitStore state access and setCommitMessage logic
to locate the change.
In `@src/renderer/stores/useGitStore.ts`:
- Around line 89-96: The generation lifecycle in useGitStore is using the
current active workspace instead of the workspace that started the run, so
cancellation and stream filtering can hit the wrong commit message. Update the
generation-scoped tracking in useGitStore to remember the originating workspace
id when the run begins, then use that stored id in workspace.onChanged,
cancelCommitMessage(), and any delta/response filtering so only the in-flight
workspace is canceled or updated.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: fbf17dc2-ed7c-4d15-859a-e63a5ba3c277
📒 Files selected for processing (10)
src/main/ipc/gitHandlers.tssrc/main/ipc/index.tssrc/main/managers/AgentManager.tssrc/main/managers/GitManager.tssrc/preload/index.tssrc/renderer/features/git/GitPanel.tsxsrc/renderer/stores/useGitStore.tssrc/shared/constants.tssrc/shared/ipc-channels.tssrc/shared/types.ts
| try { | ||
| const prompt = buildCommitPrompt(ctx); | ||
| if (prompt.length > AGENT_LIMITS.promptMax) { | ||
| // Belt + braces: GitManager's commitGen caps keep us far below this. | ||
| throw new Error('Commit context too large.'); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -n 'promptMax' src/shared/constants.tsRepository: BotCoder254/limboo
Length of output: 181
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== constants ==\n'
sed -n '1,120p' src/shared/constants.ts
printf '\n== AgentManager around buildCommitPrompt call ==\n'
sed -n '930,995p' src/main/managers/AgentManager.ts
printf '\n== buildCommitPrompt definition/usages ==\n'
rg -n "function buildCommitPrompt|const buildCommitPrompt|buildCommitPrompt\(" src/main src/shared -n -A40 -B20
printf '\n== GIT_LIMITS commitGen ==\n'
rg -n "commitGen|diffCharsMax" src -n -A20 -B10Repository: BotCoder254/limboo
Length of output: 43871
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== GitManager buildCommitContext around file/subject shaping ==\n'
sed -n '292,380p' src/main/managers/GitManager.ts
printf '\n== any truncation/redaction helpers used there ==\n'
rg -n "redactRemote|sanitize|truncate|branch" src/main/managers/GitManager.ts -n -A8 -B8
printf '\n== Git status shape and branch source ==\n'
rg -n "interface GitStatus|type GitStatus|branch:" src/shared src/main -n -A20 -B10Repository: BotCoder254/limboo
Length of output: 50374
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== GitFileChange / status parsing ==\n'
rg -n "interface GitFileChange|parseStatus\\(|files:" src/shared src/main -n -A30 -B10
printf '\n== any explicit path-length caps in git status/context flow ==\n'
rg -n "pathMax|file.*Max|status.*Max|commitGen|filesMax|branchMax|refNameMax" src/main src/shared -n -A4 -B4Repository: BotCoder254/limboo
Length of output: 50374
Cap the non-diff parts of the commit prompt. buildCommitPrompt() still includes up to 200 staged paths and 20 recent subjects, and those strings aren’t length-capped here. A large repo can still push the prompt past promptMax even when the diff stays within commitGen.diffCharsMax.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/main/managers/AgentManager.ts` around lines 962 - 967, Cap the non-diff
sections of the commit prompt in buildCommitPrompt() so staged paths and recent
subjects cannot push the prompt over AGENT_LIMITS.promptMax. Update the prompt
assembly in AgentManager to truncate or limit the 200 staged paths and 20 recent
subjects before concatenation, and keep the existing prompt.length guard as a
final safety check. Locate the change in buildCommitPrompt() and the commit
prompt generation path used by commitGen so the full prompt stays bounded even
when the diff is already within commitGen.diffCharsMax.
|
|
||
| const status = await this.status(workspaceId); | ||
| const stagedFiles = status.files.filter((f) => f.staged).slice(0, caps.filesMax); | ||
| if (stagedFiles.length === 0) { | ||
| return { root, branch: status.branch, files: [], diff: '', diffTruncated: false, recentSubjects: [] }; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Staged-file list is silently capped with no truncation signal.
Files beyond caps.filesMax are dropped via .slice(), but unlike the diff (diffTruncated), there's no equivalent flag — so buildCommitPrompt's Staged files (${ctx.files.length}) header can under-report the true count without any indication to the model (or user) that entries were omitted.
Consider adding a filesTruncated boolean to GitCommitContext mirroring diffTruncated, so the prompt can note it explicitly on very large commits.
Also applies to: 334-347
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/main/managers/GitManager.ts` around lines 296 - 301, The staged-file list
is being silently truncated by the `.slice(0, caps.filesMax)` in GitManager’s
commit context assembly, so add a `filesTruncated` flag to `GitCommitContext`
and set it when staged files exceed the cap. Update the context-building logic
that returns from `status(...)`/commit prompt preparation to carry this boolean
alongside `diffTruncated`, and adjust `buildCommitPrompt` to explicitly mention
when staged files were omitted so the `Staged files (...)` header does not
under-report without warning.
| const diffRes = await runGit(root, ['diff', '--cached', '--no-color', '--no-ext-diff'], { | ||
| maxBuffer: GIT_LIMITS.diffBytesMax + 1024, | ||
| }); | ||
| const rawDiff = diffRes.stdout; | ||
| const diffTruncated = rawDiff.length > caps.diffCharsMax; | ||
| const diff = redactRemote(diffTruncated ? rawDiff.slice(0, caps.diffCharsMax) : rawDiff); |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Redact before truncating, not after.
redactRemote is applied to the diff after it's sliced to diffCharsMax. If the cut lands mid-credential (e.g. https://<token>@host/... truncated right before the @), the redaction regex — which requires the trailing @ to match — won't catch the partial token, and it flows into the sub-agent prompt un-redacted.
🔒 Proposed fix: redact the full diff, then truncate
const rawDiff = diffRes.stdout;
- const diffTruncated = rawDiff.length > caps.diffCharsMax;
- const diff = redactRemote(diffTruncated ? rawDiff.slice(0, caps.diffCharsMax) : rawDiff);
+ const redactedDiff = redactRemote(rawDiff);
+ const diffTruncated = redactedDiff.length > caps.diffCharsMax;
+ const diff = diffTruncated ? redactedDiff.slice(0, caps.diffCharsMax) : redactedDiff;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const diffRes = await runGit(root, ['diff', '--cached', '--no-color', '--no-ext-diff'], { | |
| maxBuffer: GIT_LIMITS.diffBytesMax + 1024, | |
| }); | |
| const rawDiff = diffRes.stdout; | |
| const diffTruncated = rawDiff.length > caps.diffCharsMax; | |
| const diff = redactRemote(diffTruncated ? rawDiff.slice(0, caps.diffCharsMax) : rawDiff); | |
| const diffRes = await runGit(root, ['diff', '--cached', '--no-color', '--no-ext-diff'], { | |
| maxBuffer: GIT_LIMITS.diffBytesMax + 1024, | |
| }); | |
| const rawDiff = diffRes.stdout; | |
| const redactedDiff = redactRemote(rawDiff); | |
| const diffTruncated = redactedDiff.length > caps.diffCharsMax; | |
| const diff = diffTruncated ? redactedDiff.slice(0, caps.diffCharsMax) : redactedDiff; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/main/managers/GitManager.ts` around lines 313 - 318, The diff handling in
GitManager’s cached diff flow is redacting too late, so partial credentials can
survive truncation. Update the logic around runGit, rawDiff, and redactRemote so
the full raw diff is passed through redactRemote first, and only then slice the
redacted result to caps.diffCharsMax if needed. Keep the existing diffTruncated
behavior, but make sure the final diff assigned for the sub-agent prompt always
comes from the redacted text, not the pre-redaction slice.
| useEffect(() => { | ||
| setMessage((m) => (m === '' && template ? template : m)); | ||
| const { commitMessage, setCommitMessage } = useGitStore.getState(); | ||
| if (commitMessage === '' && template) setCommitMessage(template); | ||
| }, [template]); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Map the relevant file first, then inspect the specific sections.
ast-grep outline src/renderer/features/git/GitPanel.tsx --view expanded || true
ast-grep outline src/renderer/stores/useGitStore.ts --view expanded || true
echo '--- GitPanel.tsx (around the effect) ---'
sed -n '300,335p' src/renderer/features/git/GitPanel.tsx
echo '--- useGitStore.ts (workspace switch / hydrate path) ---'
rg -n "workspace\.onChanged|commitMessage|commitMessageTemplate|setCommitMessage|hydrate|status" src/renderer/stores/useGitStore.ts src/renderer -n -A 3 -B 3Repository: BotCoder254/limboo
Length of output: 50375
Template isn't reapplied after workspace switches. commitMessage is cleared in the workspace-change handler, but this effect only depends on template, so the draft won’t be seeded again for a newly active workspace unless the setting changes. Key it to the active workspace, or seed the template during the workspace reset.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/renderer/features/git/GitPanel.tsx` around lines 319 - 322, The commit
message template seeding in GitPanel is only triggered by changes to template,
so it won’t run again when the active workspace changes and commitMessage is
reset. Update the useEffect in GitPanel to also depend on the active workspace
state (or move the template seeding into the workspace reset handler) so the
draft is reinitialized for each newly active workspace; use the existing
useGitStore state access and setCommitMessage logic to locate the change.
| /** | ||
| * Generation-scoped bookkeeping (module-local, not store state): the user's | ||
| * pre-generation draft — restored when a run errors/cancels before any text | ||
| * arrived — and whether the in-flight run produced at least one delta. | ||
| */ | ||
| let draftBackup = ''; | ||
| let sawDelta = false; | ||
|
|
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the relevant store and related workspace/generation code.
git ls-files | rg 'src/renderer/stores/useGitStore\.ts|src/.*workspace|src/.*git|src/main/.*agent|src/.*/gitApi|src/.*/AgentManager'
echo '--- useGitStore.ts outline ---'
ast-grep outline src/renderer/stores/useGitStore.ts --view expanded
echo '--- relevant symbols / calls in useGitStore.ts ---'
rg -n "generatingMessage|commitMessage|draftBackup|sawDelta|cancelCommitMessage|generateCommitMessage|workspace\.onChanged|activeWs\(" src/renderer/stores/useGitStore.ts
echo '--- surrounding lines around workspace.onChanged and cancel/generate ---'
sed -n '120,230p' src/renderer/stores/useGitStore.ts
echo '--- workspace active id helper and change event definitions ---'
rg -n "function activeWs|const activeWs|activeWs\(|workspace\.onChanged|onChanged\(" src/renderer -g '!**/dist/**' -g '!**/build/**'
echo '--- gitApi / cancellation endpoint wiring ---'
rg -n "cancelCommitMessage|generateCommitMessage" src -g '!**/dist/**' -g '!**/build/**'
echo '--- agent-side commit generation bookkeeping ---'
rg -n "commitGenRuns|cancelCommitMessage|generateCommitMessage|workspaceId" src/main src -g '!**/dist/**' -g '!**/build/**'Repository: BotCoder254/limboo
Length of output: 50375
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- useGitStore.ts focused snippet ---'
sed -n '74,215p' src/renderer/stores/useGitStore.ts
echo '--- useWorkspaceStore.ts focused snippet ---'
sed -n '1,140p' src/renderer/stores/useWorkspaceStore.ts
echo '--- AgentManager commit generation / cancel logic ---'
sed -n '911,1065p' src/main/managers/AgentManager.ts
echo '--- git IPC handlers for commit generation ---'
sed -n '100,130p' src/main/ipc/gitHandlers.tsRepository: BotCoder254/limboo
Length of output: 17230
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- who hydrates workspace/git stores ---'
rg -n "useWorkspaceStore\.getState\(\)\.hydrate|useGitStore\.getState\(\)\.hydrate|hydrate\(\)" src/renderer -g '!**/dist/**' -g '!**/build/**'
echo '--- workspace event contract in preload/shared/main ---'
rg -n "onChanged\(" src/preload src/main src/shared -g '!**/dist/**' -g '!**/build/**'
echo '--- workspace manager changed event emission ---'
rg -n "broadcast.*workspace|onChanged|WorkspaceChanged|activeId" src/main/managers src/main/ipc src/preload -g '!**/dist/**' -g '!**/build/**'Repository: BotCoder254/limboo
Length of output: 1508
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- workspace event types and bridge wiring ---'
sed -n '1,220p' src/shared/types.ts
echo '--- preload workspace bridge ---'
sed -n '1,260p' src/preload/index.ts
echo '--- main workspace manager active-change emission ---'
rg -n "onChanged|changed|setActive|activeId|broadcast" src/main/managers/workspace src/main/ipc/workspaceHandlers.ts src/main/index.ts -g '!**/dist/**' -g '!**/build/**'Repository: BotCoder254/limboo
Length of output: 21920
Track the generating workspace, not activeWs()
workspace.onChanged runs after activeId has already switched, so cancelCommitMessage() targets the new workspace instead of the in-flight run. That leaves the old generation running and lets its later stream frames mutate commitMessage if the user switches back. Keep the originating workspace id with the run and use it for cancel/filtering.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/renderer/stores/useGitStore.ts` around lines 89 - 96, The generation
lifecycle in useGitStore is using the current active workspace instead of the
workspace that started the run, so cancellation and stream filtering can hit the
wrong commit message. Update the generation-scoped tracking in useGitStore to
remember the originating workspace id when the run begins, then use that stored
id in workspace.onChanged, cancelCommitMessage(), and any delta/response
filtering so only the in-flight workspace is canceled or updated.
Summary
Deepens the git integration with a commit-message sub-agent driven by the same Claude Agent SDK that powers the main agent, plus a redesigned commit bar — all inside the existing UI/theme and hardened per the repo's security contract.
Git sub-agent (isolated one-shot, streams live)
GitManager.buildCommitContext()assembles everything main-side: staged diff (capped 60 KB), binary-flagged file list, recent commit subjects for style inference — argv-only git, credentials redacted.AgentManager.generateCommitMessage()runs a single-turn, tool-less SDK query (pinned Haiku model,allowedTools: []and deny-allcanUseTool, no resume, no transcript, no MCP servers, no lifecycle changes). Deltas stream over a new dedicatedgit:commit-message-streamevent straight into the commit textarea; cancelable per workspace.Commit bar (same design language)
Commit Nbutton + bordered Stage all / Unstage all buttons + Generate ⇄ Cancel in one control row; textarea goes read-only while streaming; Cmd/Ctrl+Enter unchanged.useGitStore(survives tab switches); failed/canceled runs restore the pre-generation draft; workspace switches cancel in-flight runs.Security hardening
handle()wrapper; only renderer input is a workspace id (assertId).GIT_LIMITS.commitGen; prompt double-checked againstAGENT_LIMITS.promptMax; errors/stderr redacted.Test plan
npx vite build --config vite.renderer.config.mtspassesnpm run lintcleannpm startboots with all Forge targets built, IPC registered, no errorsdark:, no gradients, no off-palette hex, noshell: true🤖 Generated with Claude Code
Note
Medium Risk
New agent IPC path sends staged diffs to the model (credentials redacted, tools disabled, no auto-commit), but it expands sensitive code exposure to the LLM and adds concurrent SDK usage beside chat runs.
Overview
Adds AI-assisted commit messages in the Git panel: the renderer only sends a workspace id; the main process builds capped, redacted context from staged changes and runs an isolated, tool-less Claude SDK one-shot that proposes text only (Commit still goes through
git commit).Main process:
GitManager.buildCommitContext()gathers staged diff, file list, and recent subjects underGIT_LIMITS.commitGen.AgentManager.generateCommitMessage()uses a pinned Haiku model, streams deltas ongit:commit-message-stream, and supports per-workspace cancel without touching session agent lifecycle. New IPC generate/cancel handlers wireGitManager+AgentManager.Renderer: Commit draft lives in
useGitStorewith live streaming into the textarea; Generate/Cancel, Stage all/Unstage all, and read-only-while-generating behavior inGitPanel. Failed or canceled runs restore the pre-generation draft; workspace switches cancel in-flight generation.Reviewed by Cursor Bugbot for commit 9546559. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by CodeRabbit
New Features
Bug Fixes